package com.shopx.core.utils;


import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class ClazzUtil {
//	private static final ShopxLogger logger = LoggerFactory.getLogger(ClazzUtil.class);

	private static final Map<String, Field[]> clazz2fields = new ConcurrentHashMap<String, Field[]>();
	private static final Map<String, Map<String, Field>> clazz2fieldMap = new ConcurrentHashMap<String, Map<String, Field>>();

	private static Field[] getCopyableField(Class<?> clazz) {
		String clazzName = clazz.getName();
		if (!clazz2fields.containsKey(clazzName)) {
			Field[] fields = Reflector.getAllField(clazz);
			List<Field> list = new ArrayList<Field>();
			for (Field field : fields) {
				if (!Modifier.isFinal(field.getModifiers())) {
					field.setAccessible(true);
					list.add(field);
				}
			}
			clazz2fields.put(clazzName, list.toArray(new Field[list.size()]));
		}
		return clazz2fields.get(clazzName);
	}

	private static Map<String, Field> getCopyableFieldMap(Class<?> clazz) {
		String clazzName = clazz.getName();
		if (!clazz2fieldMap.containsKey(clazzName)) {
			Field[] fields = Reflector.getAllField(clazz);
			Map<String, Field> map = new ConcurrentHashMap<String, Field>();
			for (Field field : fields) {
				if (!Modifier.isFinal(field.getModifiers())) {
					field.setAccessible(true);
					map.put(field.getName(), field);
				}
			}
			clazz2fieldMap.put(clazzName, map);
		}
		return clazz2fieldMap.get(clazzName);
	}

	public static Object copy(Object from, Object to, String... ignoreProperties) {
		//logger.info("ClazzUtil.copy.from:{},to:{}", from,to);
    Field[] fromFields = getCopyableField(from.getClass());
    Map<String, Field> toFieldMap = getCopyableFieldMap(to.getClass());
    for (Field fromField : fromFields) {
      String fromFieldName = fromField.getName();
      Field toField = toFieldMap.get(fromFieldName);
      if (toField == null || isIgnore(fromFieldName, ignoreProperties)) {
        continue;
      }
      try {
        Object val = fromField.get(from);
        setFieldValue(to, toField, val);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      }
    }
		return to;
	}

	private static void setFieldValue(Object obj, Field field, Object val) throws IllegalAccessException {
		if (val == null) {
			field.set(obj, null);
		} else if (field.getType().isInstance(val)) {
			field.set(obj, val);
		} else if (val.getClass().isPrimitive()) {
			setSimpleFieldValue(obj, field, val);
		} else if (val instanceof Number) {
			setSimpleFieldValue(obj, field, val);
		} else if (val instanceof Character) {
			setSimpleFieldValue(obj, field, val);
		} else if (val instanceof Boolean) {
			setSimpleFieldValue(obj, field, val);
		} else if (val instanceof String) {
			setSimpleFieldValue(obj, field, val);
		} else if (val instanceof Date) {
			setSimpleFieldValue(obj, field, val);
		} else if (val instanceof Enum) {
			setEnumFieldValue(obj, field, val);
		} else {
			setComplexFieldValue(obj, field, val);
		}
	}

	private static void setSimpleFieldValue(Object obj, Field field, Object val) throws IllegalAccessException {
		Class<?> fieldClazz = field.getType();
		if (fieldClazz.isEnum()) {
			field.set(obj, Enum.valueOf((Class<Enum>) fieldClazz, val.toString()));
		} else {
			field.set(obj, val);
		}
	}

	@SuppressWarnings("rawtypes")
	private static void setEnumFieldValue(Object obj, Field field, Object val) throws IllegalAccessException {
		Class<?> fieldClazz = field.getType();
		if (fieldClazz.isEnum()) {
			field.set(obj, val);
		} else if (fieldClazz == String.class) {
			field.set(obj, ((Enum) val).name());
		} else {
			field.set(obj, val);
		}
	}

	private static void setComplexFieldValue(Object obj, Field field, Object val) throws IllegalAccessException {
		Class<?> clazz = field.getType();
		Object fieldObj = Reflector.create(clazz);
		copy(val, fieldObj);
		field.set(obj, fieldObj);
	}

	private static boolean isIgnore(String fieldName, String[] ignoreProperties) {
		if (ignoreProperties == null || ignoreProperties.length == 0) {
			return false;
		} else {
			for (String property : ignoreProperties) {
				if (fieldName.equalsIgnoreCase(property)) {
					return true;
				}
			}
			return false;
		}
	}
}
